package net.gdface.sdk;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.gdface.annotation.DeriveMethod;
import net.gdface.annotation.GenericParam;
import net.gdface.image.ImageErrorException;
import net.gdface.image.LazyImage;
import net.gdface.utils.Assert;
import net.gdface.utils.Judge;

/**
 * 
 * 人脸识别SDK核心接口<br>
 * 此接口中所有byte[]类型的图像参数都指未解码的图像格式(如jpg,bmp,png...),<br>
 * 目前支持的图像格式类型依赖于{@link javax.imageio.ImageIO}的支持能力
 * @author guyadong
 */
public interface FaceApi {

	/**
	 * 对人脸图像提取特征码，返回比较相似度结果<br>
	 * imgData1和imgData2相等或imgData2为{@code null}时,即比较同一张图像中的两张人脸的相似度
	 * 调用该方法时假设图像({@code imgData1}和{@code imgData2})能正常解码,<br>
	 * 所以当对图像解码出现异常时，将{@link ImageErrorException}异常对象封装到{@link RuntimeException}抛出<br>
	 * 任何参数为{@code null}则抛出{@link IllegalArgumentException}
	 * @param imgData1
	 *            　图像1数据字节数组
	 * @param facePos1
	 *            检测到的人脸/眼睛位置
	 * @param imgData2
	 *            　图像1数据字节数组
	 * @param facePos2
	 *            检测到的人脸/眼睛位置
	 * @return 两张人脸之间的相似度(0.0~1)
	 * @throws NotFaceDetectedException
	 * @throws ImageErrorException
	 * @see #getCodeInfo(byte[], int, CodeInfo[])
	 * @see #compareCode(byte[], byte[])
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract double compare2Face(
			@GenericParam(name="T1")byte[] imgData1, 
			CodeInfo facePos1, 
			@GenericParam(name="T2")byte[] imgData2, 
			CodeInfo facePos2)
			throws NotFaceDetectedException, ImageErrorException;

	/**
	 * 对两个图像指定范围({@link FRect})进行人脸检测,找到并提取唯一的人脸特征码,然后比较相似度,返回相似度结果<br>
	 * imgData1和imgData2相等或imgData2为{@code null}时,即比较同一张图像中的两张人脸的相似度,这种情况下detectRect1和detectRect2都不能为{@code null}
	 * @param imgData1
	 *            图像1数据字节数组,为{@code null}则抛出{@link IllegalArgumentException}
	 * @param detectRect1
	 *            图片检测范围，为null时全图检测
	 * @param imgData2
	 *            图像2数据字节数组
	 * @param detectRect2
	 *            图片检测范围，为null时全图检测
	 * @return 两张人脸之间的相似度(0.0~1)
	 * @throws ImageErrorException
	 * @throws NotFaceDetectedException
	 * @see #detectAndGetCodeInfo(byte[], int, FRect)
	 * @see #compareCode(byte[], byte[])
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract double detectAndCompare2Face(
			@GenericParam(name="T1")byte[] imgData1, 
			FRect detectRect1, 
			@GenericParam(name="T2")byte[] imgData2, 
			FRect detectRect2)
			throws ImageErrorException, NotFaceDetectedException;

	/**
	 * 特征码比对<br>
	 * 参数为{@code null}或长度不一致则抛出{@link IllegalArgumentException}异常
	 * @param code1 待比对的特征码
	 * @param code2 待比对的特征码
	 * @return 两个特征码之间的相似度（0.0~1）
	 */
	public abstract double compareCode(@GenericParam(name="T2")byte[] code1, @GenericParam(name="T2")byte[] code2);

	/**
	 * 特征码比对1:N<br>
	 * @param code1
	 * @param codes 包含人脸特征的{@link CodeInfo }数组
	 * @return 返回对应的特征码相似度数组
	 */
	public abstract double[] compareCodes(byte[] code1, CodeInfo[] codes);
	/**
	 * 特征码比对1:N<br>
	 * @param code1
	 * @param codes 一组人脸特征
	 * @return 返回对应的特征码相似度列表
	 */
	public abstract List<Double> compareFeatures(byte[] code1, List<byte[]> codes);

	/**
	 * 先对图像数据{@code imgData}进行人脸检测，然后提取人脸特征码<br>
	 * 
	 * @param imgData
	 *            图像数据字节数组,为{@code null}则抛出{@link IllegalArgumentException}
	 * @param faceNum
	 *            参见 {@link #getCodeInfo(byte[], int, CodeInfo[])}
	 * @param detectRectangle
	 *            参见 {@link #detectFace(byte[], FRect)}
	 * @return 返回所有成功提取特征码的{@link CodeInfo}对象<br>
	 *         与{@link #getCodeInfo(byte[], int, CodeInfo[])}的返回结果有差别，返回结果中不包含检测到人脸但提取特征码失败的对象
	 * @throws ImageErrorException
	 * @throws NotFaceDetectedException 没有检测人脸或提取到特征码的人脸数目不是要求的人脸数目(faceNum>0)
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract CodeInfo[] detectAndGetCodeInfo(byte[] imgData, int faceNum, FRect detectRectangle)
			throws ImageErrorException, NotFaceDetectedException;

	/**
	 * 对图像({@code imgData})进行人脸检测，返回人脸位置数据对象{@link FRect}数组<br>
	 * 
	 * @param imgData
	 *            图像数据字节数组,为{@code null}则抛出{@link IllegalArgumentException}
	 * @param detectRectangle
	 *            图片检测范围，为null时全图检测
	 * @return 没有检测到人脸则返回空数组
	 * @throws ImageErrorException
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract CodeInfo[] detectFace(byte[] imgData, FRect detectRectangle) throws ImageErrorException;

	/**
	 * 对图像({@code imgData})中可能存在人脸的一组区域{@code detectRects}进行人脸检测,<br>
	 * 返回实际检测到人脸位置的数据对象{@link FRect}数组.<br>
	 * 当使用第三方算法进行人脸检测后，调用 {@link #getCodeInfo(byte[], int, CodeInfo[])}提取特征码前要使用本方法再次检测人脸，以获取准确的人脸定位信息<br>
	 * 
	 * 调用该方法时假设图像({@code imgData})能正常解码,所以当对图像解码出现异常时，将{@link ImageErrorException}异常对象封装到{@link RuntimeException}抛出<br>
	 * 
	 * @param imgData
	 *            图像数据字节数组,为{@code null}则抛出{@link IllegalArgumentException}
	 * @param detectRects
	 *            可能存在人脸的区域对象(假设每个区域只有一张人脸)<br>
	 *            为{@code null}时抛出{@link IllegalArgumentException}<br>
	 * @return 返回的对象数组与参数{@code detectRects}中的元素下标一一对应<br>
	 *         如果 {@code detectRects}元素指定的区域没有检测到人脸或检测到的人脸不唯一，则返回{@code null}
	 * @see #detectFace(byte[], FRect)
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract CodeInfo[] detectFaceAgain(byte[] imgData, FRect[] detectRects);

	/**
	 * 检测最中心的人脸
	 * @param imgData
	 * @return
	 * @throws NotFaceDetected
	 * @throws ImageError
	 */
	public abstract CodeInfo detectCenterFace(byte[] imgData) throws NotFaceDetectedException, ImageErrorException;

	/**
	 * 根据{@code facePos}提供的人脸信息位置, 在{@code imgData}图像中提取特征码<br>
	 * 与 {@link #detectAndGetCodeInfo(byte[], int, FRect)}不同, 本方法不对图像数据{@code imgData}进行人脸检测,<br>
	 * 假设{@code facePos}是 {@link #detectFace(byte[], FRect)}或 {@link #detectFaceAgain(byte[], FRect[])} 的返回结果<br>
	 * @param imgData
	 *           图像数据字节数组,为{@code null}则抛出 {@link IllegalArgumentException} 
	 * @param faceNum
	 *            要求返回的人脸特征码数目<br>
	 *            大于0时，如果实际提取到的人脸特征码数目不等于{@code faceNum},则抛出{@link NotFaceDetectedException}<br>
	 *            小于等于0时，返回所有提取到到人脸特征码 
	 * @param facePos
	 *            检测到的人脸位置对象列表<br>
	 *            为{@code null}或数组长度为0时抛出{@link IllegalArgumentException}<br>
	 *            如果元素为{@code null},则跳过<br>
	 * @return 返回facePos<br>	         
	 *         如果没有提取到特征码,则对应元素{@link CodeInfo#getCode()}返回{@code null}
	 * @throws NotFaceDetectedException 提取特征码的人脸数目为0或没有提取到指定数目(faceNum>0)的特征码
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract CodeInfo[] getCodeInfo(byte[] imgData, int faceNum, CodeInfo[] facePos) throws NotFaceDetectedException;

	/**
	 * 多张人脸提取特征
	 * @param faces
	 * @return
	 * @throws NotFaceDetectedException 
	 */
	public abstract byte[] getFeature(Map<ByteBuffer,CodeInfo>faces) throws NotFaceDetectedException;
 	/**
	 * 判断图像是否能提取到人脸特征码
	 * 
	 * @param imgData
	 *            图像数据字节数组,为{@code null}则抛出{@link IllegalArgumentException}
	 * @param detectRectangle
	 * @return 为{@code true}则能检测到人脸并能提取特征码
	 * @throws ImageErrorException
	 * @see #detectAndGetCodeInfo(byte[], int, FRect)
	 */
	@DeriveMethod(localResolvedTypes={InputStream.class,URL.class,File.class,ByteBuffer.class})
	public abstract boolean hasFace(byte[] imgData, FRect detectRectangle) throws ImageErrorException;

	/**
	 * 是否支持多张人脸合成特征
	 * @return
	 */
	public abstract boolean multiFaceFeature();
	/**
	 * 返回当前接口实现方式<br> 
	 * false WebService类型<br>
	 * true 本地实现
	 * @return　boolean
	 */
	public abstract boolean isLocal();
	/**
	 * {@link FaceApi}抽象实现,SDK接口类必须从此类派生<br>
	 * @author guyadong
	 *
	 */
	public abstract static class BaseFaceApi implements FaceApi{
		@Override
		public double compare2Face(byte[] imgData1, CodeInfo facePos1, byte[] imgData2, CodeInfo facePos2)
				throws NotFaceDetectedException {
			byte[] code1, code2;
			Assert.notNull(facePos1, "facePos1");
			Assert.notNull(facePos2, "facePos2");
			if (imgData1 == imgData2||null==imgData2) {
				CodeInfo[] codes = getCodeInfo(imgData1, 2, new CodeInfo[] { facePos1, facePos2 });			
				code1 = codes[0].getCode();
				code2 = codes[1].getCode();
			} else {
				code1 = getCodeInfo(imgData1, 1, new CodeInfo[] { facePos1 })[0].getCode();
				code2 = getCodeInfo(imgData2, 1, new CodeInfo[] { facePos2 })[0].getCode();
			}
			return compareCode(code1, code2);
		}
		@Override
		public double detectAndCompare2Face(byte[] imgData1, FRect detectRect1, byte[] imgData2, FRect detectRect2)
				throws ImageErrorException, NotFaceDetectedException {
			byte[] code1, code2;
			if (imgData1 == imgData2||null==imgData2) {
				if (detectRect1 == null || null == detectRect2)
					throw new IllegalArgumentException("detectRect1,detectRect2 must not be null");
				CodeInfo[] codes = detectFaceAgain(imgData1, new FRect[] { detectRect1, detectRect2 });
				// 如果任何一个没有检测到人脸,则抛出异常
				if(Judge.hasNull(codes)){
					throw new NotFaceDetectedException();
				}
				codes = getCodeInfo(imgData1, 2, codes);
				code1 = codes[0].getCode();
				code2 = codes[1].getCode();
			} else {
				code1 = detectAndGetCodeInfo(imgData1, 1, detectRect1)[0].getCode();
				code2 = detectAndGetCodeInfo(imgData2, 1, detectRect2)[0].getCode();
			}
			return compareCode(code1, code2);
		}
		@Override
		public List<Double> compareFeatures(byte[] code1,List<byte[]> codes){
			if(null == code1 || null == codes){
				throw new NullPointerException("code1 or codes is null");
			}
			ArrayList<Double> result = new ArrayList<Double>(codes.size());
			for(byte[] code2:codes){
				result.add(compareCode(code1,code2));
			}
			return result;
		}
		@Override
		public double[] compareCodes(byte[] code1,CodeInfo[] codes){
			if(null == code1 || null == codes){
				throw new NullPointerException("code1 or codes is null");
			}
			double[] result = new double[codes.length];
			for(int i = 0;i<result.length;++i){
				result[i] = compareCode(code1,codes[i].getCode());
			}
			return result;
		}
		@Override
		public CodeInfo detectCenterFace(byte[] imgData) throws NotFaceDetectedException, ImageErrorException {
			LazyImage lazyImg = new LazyImage(imgData).open();
			CodeInfo centerFace;
			CodeInfo[] codes = detectFace(imgData, null);
			if(codes.length == 0){
				throw new NotFaceDetectedException();
			}
			FInt2 center=new FInt2(lazyImg.getWidth()>>1,lazyImg.getHeight()>>1);
			centerFace=codes[0];
			double minDistance = RectUtils.distance(centerFace.getPos(), center);
			for(int i=1;i<codes.length;++i){
				CodeInfo f=codes[i];
				double d = RectUtils.distance(f.getPos(), center);
				if(d<minDistance){
					centerFace=f;
					minDistance=d;
				}
			}		
			return centerFace;
		}
		@Override
		public boolean hasFace(byte[] imgData, FRect detectRectangle) throws ImageErrorException {
			try {
				detectAndGetCodeInfo(imgData, 0, detectRectangle);
				return true;
			} catch (NotFaceDetectedException e) {
				return false;
			}
		}
		@Override
		public byte[] getFeature(Map<ByteBuffer, CodeInfo> faces) throws NotFaceDetectedException {
			throw new UnsupportedOperationException();
		}
		@Override
		public boolean multiFaceFeature() {
			return false;
		}
	}
}